home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2008 February / PCWFEB08.iso / Software / Resources / Developers / XAMPP 1.5.4 / Windows installer / xampp-win32-1.5.4-installer.exe / xampp / php / pear / PEAR / Downloader.php < prev    next >
Encoding:
PHP Script  |  2005-12-02  |  57.3 KB  |  1,465 lines

  1. <?php
  2. /**
  3.  * PEAR_Downloader, the PEAR Installer's download utility class
  4.  *
  5.  * PHP versions 4 and 5
  6.  *
  7.  * LICENSE: This source file is subject to version 3.0 of the PHP license
  8.  * that is available through the world-wide-web at the following URI:
  9.  * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
  10.  * the PHP License and are unable to obtain it through the web, please
  11.  * send a note to license@php.net so we can mail you a copy immediately.
  12.  *
  13.  * @category   pear
  14.  * @package    PEAR
  15.  * @author     Greg Beaver <cellog@php.net>
  16.  * @author     Stig Bakken <ssb@php.net>
  17.  * @author     Tomas V. V. Cox <cox@idecnet.com>
  18.  * @author     Martin Jansen <mj@php.net>
  19.  * @copyright  1997-2005 The PHP Group
  20.  * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
  21.  * @version    CVS: $Id: Downloader.php,v 1.94 2005/10/29 21:23:19 cellog Exp $
  22.  * @link       http://pear.php.net/package/PEAR
  23.  * @since      File available since Release 1.3.0
  24.  */
  25.  
  26. /**
  27.  * Needed for constants, extending
  28.  */
  29. require_once 'PEAR/Common.php';
  30.  
  31. define('PEAR_INSTALLER_OK',       1);
  32. define('PEAR_INSTALLER_FAILED',   0);
  33. define('PEAR_INSTALLER_SKIPPED', -1);
  34. define('PEAR_INSTALLER_ERROR_NO_PREF_STATE', 2);
  35.  
  36. /**
  37.  * Administration class used to download anything from the internet (PEAR Packages,
  38.  * static URLs, xml files)
  39.  *
  40.  * @category   pear
  41.  * @package    PEAR
  42.  * @author     Greg Beaver <cellog@php.net>
  43.  * @author     Stig Bakken <ssb@php.net>
  44.  * @author     Tomas V. V. Cox <cox@idecnet.com>
  45.  * @author     Martin Jansen <mj@php.net>
  46.  * @copyright  1997-2005 The PHP Group
  47.  * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
  48.  * @version    Release: 1.4.5
  49.  * @link       http://pear.php.net/package/PEAR
  50.  * @since      Class available since Release 1.3.0
  51.  */
  52. class PEAR_Downloader extends PEAR_Common
  53. {
  54.     /**
  55.      * @var PEAR_Registry
  56.      * @access private
  57.      */
  58.     var $_registry;
  59.  
  60.     /**
  61.      * @var PEAR_Remote
  62.      * @access private
  63.      */
  64.     var $_remote;
  65.  
  66.     /**
  67.      * Preferred Installation State (snapshot, devel, alpha, beta, stable)
  68.      * @var string|null
  69.      * @access private
  70.      */
  71.     var $_preferredState;
  72.  
  73.     /**
  74.      * Options from command-line passed to Install.
  75.      *
  76.      * Recognized options:<br />
  77.      *  - onlyreqdeps   : install all required dependencies as well
  78.      *  - alldeps       : install all dependencies, including optional
  79.      *  - installroot   : base relative path to install files in
  80.      *  - force         : force a download even if warnings would prevent it
  81.      *  - nocompress    : download uncompressed tarballs
  82.      * @see PEAR_Command_Install
  83.      * @access private
  84.      * @var array
  85.      */
  86.     var $_options;
  87.  
  88.     /**
  89.      * Downloaded Packages after a call to download().
  90.      *
  91.      * Format of each entry:
  92.      *
  93.      * <code>
  94.      * array('pkg' => 'package_name', 'file' => '/path/to/local/file',
  95.      *    'info' => array() // parsed package.xml
  96.      * );
  97.      * </code>
  98.      * @access private
  99.      * @var array
  100.      */
  101.     var $_downloadedPackages = array();
  102.  
  103.     /**
  104.      * Packages slated for download.
  105.      *
  106.      * This is used to prevent downloading a package more than once should it be a dependency
  107.      * for two packages to be installed.
  108.      * Format of each entry:
  109.      *
  110.      * <pre>
  111.      * array('package_name1' => parsed package.xml, 'package_name2' => parsed package.xml,
  112.      * );
  113.      * </pre>
  114.      * @access private
  115.      * @var array
  116.      */
  117.     var $_toDownload = array();
  118.  
  119.     /**
  120.      * Array of every package installed, with names lower-cased.
  121.      *
  122.      * Format:
  123.      * <code>
  124.      * array('package1' => 0, 'package2' => 1, );
  125.      * </code>
  126.      * @var array
  127.      */
  128.     var $_installed = array();
  129.  
  130.     /**
  131.      * @var array
  132.      * @access private
  133.      */
  134.     var $_errorStack = array();
  135.     
  136.     /**
  137.      * @var boolean
  138.      * @access private
  139.      */
  140.     var $_internalDownload = false;
  141.  
  142.     /**
  143.      * Temporary variable used in sorting packages by dependency in {@link sortPkgDeps()}
  144.      * @var array
  145.      * @access private
  146.      */
  147.     var $_packageSortTree;
  148.  
  149.     /**
  150.      * Temporary directory, or configuration value where downloads will occur
  151.      * @var string
  152.      */
  153.     var $_downloadDir;
  154.     // {{{ PEAR_Downloader()
  155.  
  156.     /**
  157.      * @param PEAR_Frontend_*
  158.      * @param array
  159.      * @param PEAR_Config
  160.      */
  161.     function PEAR_Downloader(&$ui, $options, &$config)
  162.     {
  163.         parent::PEAR_Common();
  164.         $this->_options = $options;
  165.         $this->config = &$config;
  166.         $this->_preferredState = $this->config->get('preferred_state');
  167.         $this->ui = &$ui;
  168.         if (!$this->_preferredState) {
  169.             // don't inadvertantly use a non-set preferred_state
  170.             $this->_preferredState = null;
  171.         }
  172.  
  173.         if (isset($this->_options['installroot'])) {
  174.             $this->config->setInstallRoot($this->_options['installroot']);
  175.         }
  176.         $this->_registry = &$config->getRegistry();
  177.         $this->_remote = &$config->getRemote();
  178.  
  179.         if (isset($this->_options['alldeps']) || isset($this->_options['onlyreqdeps'])) {
  180.             $this->_installed = $this->_registry->listAllPackages();
  181.             foreach ($this->_installed as $key => $unused) {
  182.                 if (!count($unused)) {
  183.                     continue;
  184.                 }
  185.                 @array_walk($this->_installed[$key], 'strtolower');
  186.             }
  187.         }
  188.     }
  189.  
  190.     /**
  191.      * Attempt to discover a channel's remote capabilities from
  192.      * its server name
  193.      * @param string
  194.      * @return boolean
  195.      */
  196.     function discover($channel)
  197.     {
  198.         $this->log(1, 'Attempting to discover channel "' . $channel . '"...');
  199.         PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
  200.         $callback = $this->ui ? array(&$this, '_downloadCallback') : null;
  201.         if (!class_exists('System')) {
  202.             require_once 'System.php';
  203.         }
  204.         $a = $this->downloadHttp('http://' . $channel . '/channel.xml', $this->ui,
  205.             System::mktemp(array('-d')), $callback, false);
  206.         PEAR::popErrorHandling();
  207.         if (PEAR::isError($a)) {
  208.             return false;
  209.         }
  210.         list($a, $lastmodified) = $a;
  211.         if (!class_exists('PEAR/ChannelFile.php')) {
  212.             require_once 'PEAR/ChannelFile.php';
  213.         }
  214.         $b = new PEAR_ChannelFile;
  215.         if ($b->fromXmlFile($a)) {
  216.             @unlink($a);
  217.             if ($this->config->get('auto_discover')) {
  218.                 $this->_registry->addChannel($b, $lastmodified);
  219.                 $alias = $b->getName();
  220.                 if ($b->getName() == $this->_registry->channelName($b->getAlias())) {
  221.                     $alias = $b->getAlias();
  222.                 }
  223.                 $this->log(1, 'Auto-discovered channel "' . $channel .
  224.                     '", alias "' . $alias . '", adding to registry');
  225.             }
  226.             return true;
  227.         }
  228.         @unlink($a);
  229.         return false;
  230.     }
  231.  
  232.     /**
  233.      * For simpler unit-testing
  234.      * @param PEAR_Downloader
  235.      * @return PEAR_Downloader_Package
  236.      */
  237.     function &newDownloaderPackage(&$t)
  238.     {
  239.         if (!class_exists('PEAR_Downloader_Package')) {
  240.             require_once 'PEAR/Downloader/Package.php';
  241.         }
  242.         $a = &new PEAR_Downloader_Package($t);
  243.         return $a;
  244.     }
  245.  
  246.     /**
  247.      * For simpler unit-testing
  248.      * @param PEAR_Config
  249.      * @param array
  250.      * @param array
  251.      * @param int
  252.      */
  253.     function &getDependency2Object(&$c, $i, $p, $s)
  254.     {
  255.         if (!class_exists('PEAR/Dependency2.php')) {
  256.             require_once 'PEAR/Dependency2.php';
  257.         }
  258.         $z = &new PEAR_Dependency2($c, $i, $p, $s);
  259.         return $z;
  260.     }
  261.  
  262.     function &download($params)
  263.     {
  264.         if (!count($params)) {
  265.             $a = array();
  266.             return $a;
  267.         }
  268.         if (!isset($this->_registry)) {
  269.             $this->_registry = &$this->config->getRegistry();
  270.         }
  271.         if (!isset($this->_remote)) {
  272.             $this->_remote = &$this->config->getRemote();
  273.         }
  274.         $channelschecked = array();
  275.         // convert all parameters into PEAR_Downloader_Package objects
  276.         foreach ($params as $i => $param) {
  277.             $params[$i] = &$this->newDownloaderPackage($this);
  278.             PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
  279.             $err = $params[$i]->initialize($param);
  280.             PEAR::staticPopErrorHandling();
  281.             if (PEAR::isError($err)) {
  282.                 if (!isset($this->_options['soft'])) {
  283.                     $this->log(0, $err->getMessage());
  284.                 }
  285.                 $params[$i] = false;
  286.                 if (is_object($param)) {
  287.                     $param = $param->getChannel() . '/' . $param->getPackage();
  288.                 }
  289.                 $this->pushError('Package "' . $param . '" is not valid',
  290.                     PEAR_INSTALLER_SKIPPED);
  291.             } else {
  292.                 if ($params[$i] && !isset($channelschecked[$params[$i]->getChannel()]) &&
  293.                       !isset($this->_options['offline'])) {
  294.                     $channelschecked[$params[$i]->getChannel()] = true;
  295.                     PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
  296.                     if (!class_exists('System')) {
  297.                         require_once 'System.php';
  298.                     }
  299.                     $curchannel = &$this->_registry->getChannel($params[$i]->getChannel());
  300.                     $a = $this->downloadHttp('http://' . $params[$i]->getChannel() .
  301.                         '/channel.xml', $this->ui,
  302.                         System::mktemp(array('-d')), null, $curchannel->lastModified());
  303.                     PEAR::staticPopErrorHandling();
  304.                     if (PEAR::isError($a) || !$a) {
  305.                         continue;
  306.                     }
  307.                     $this->log(0, 'WARNING: channel "' . $params[$i]->getChannel() . '" has ' .
  308.                         'updated its protocols, use "channel-update ' . $params[$i]->getChannel() .
  309.                         '" to update');
  310.                 }
  311.                 if ($params[$i] && !isset($this->_options['downloadonly'])) {
  312.                     $checkdir = $this->config->get('php_dir', null, $params[$i]->getChannel());
  313.                     while ($checkdir && $checkdir != '/' && !file_exists($checkdir)) {
  314.                         $checkdir = dirname($checkdir);
  315.                     }
  316.                     if ($checkdir == '.') {
  317.                         $checkdir = '/';
  318.                     }
  319.                     if (!@is_writeable($checkdir)) {
  320.                         return PEAR::raiseError('Cannot install, php_dir for channel "' .
  321.                             $params[$i]->getChannel() . '" is not writeable by the current user');
  322.                     }
  323.                 }
  324.             }
  325.         }
  326.         unset($channelschecked);
  327.         PEAR_Downloader_Package::removeDuplicates($params);
  328.         if (!count($params)) {
  329.             $a = array();
  330.             return $a;
  331.         }
  332.         if (!isset($this->_options['nodeps']) && !isset($this->_options['offline'])) {
  333.             $reverify = true;
  334.             while ($reverify) {
  335.                 $reverify = false;
  336.                 foreach ($params as $i => $param) {
  337.                     $ret = $params[$i]->detectDependencies($params);
  338.                     if (PEAR::isError($ret)) {
  339.                         $reverify = true;
  340.                         $params[$i] = false;
  341.                         PEAR_Downloader_Package::removeDuplicates($params);
  342.                         if (!isset($this->_options['soft'])) {
  343.                             $this->log(0, $ret->getMessage());
  344.                         }
  345.                         continue 2;
  346.                     }
  347.                 }
  348.             }
  349.         }
  350.         if (isset($this->_options['offline'])) {
  351.             $this->log(3, 'Skipping dependency download check, --offline specified');
  352.         }
  353.         if (!count($params)) {
  354.             $a = array();
  355.             return $a;
  356.         }
  357.         while (PEAR_Downloader_Package::mergeDependencies($params));
  358.         PEAR_Downloader_Package::removeInstalled($params);
  359.         if (!count($params)) {
  360.             $this->pushError('No valid packages found', PEAR_INSTALLER_FAILED);
  361.             $a = array();
  362.             return $a;
  363.         }
  364.         PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
  365.         $err = $this->analyzeDependencies($params);
  366.         PEAR::popErrorHandling();
  367.         if (!count($params)) {
  368.             $this->pushError('No valid packages found', PEAR_INSTALLER_FAILED);
  369.             $a = array();
  370.             return $a;
  371.         }
  372.         $ret = array();
  373.         $newparams = array();
  374.         if (isset($this->_options['pretend'])) {
  375.             return $params;
  376.         }
  377.         foreach ($params as $i => $package) {
  378.             PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
  379.             $pf = &$params[$i]->download();
  380.             PEAR::staticPopErrorHandling();
  381.             if (PEAR::isError($pf)) {
  382.                 if (!isset($this->_options['soft'])) {
  383.                     $this->log(1, $pf->getMessage());
  384.                     $this->log(0, 'Error: cannot download "' .
  385.                         $this->_registry->parsedPackageNameToString($package->getParsedPackage(),
  386.                             true) .
  387.                         '"');
  388.                 }
  389.                 continue;
  390.             }
  391.             $newparams[] = &$params[$i];
  392.             $ret[] = array('file' => $pf->getArchiveFile(),
  393.                                    'info' => &$pf,
  394.                                    'pkg' => $pf->getPackage());
  395.         }
  396.         $this->_downloadedPackages = $ret;
  397.         return $newparams;
  398.     }
  399.  
  400.     /**
  401.      * @param array all packages to be installed
  402.      */
  403.     function analyzeDependencies(&$params)
  404.     {
  405.         $hasfailed = $failed = false;
  406.         if (isset($this->_options['downloadonly'])) {
  407.             return;
  408.         }
  409.         PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
  410.         $redo = true;
  411.         $reset = false;
  412.         while ($redo) {
  413.             $redo = false;
  414.             foreach ($params as $i => $param) {
  415.                 $deps = $param->getDeps();
  416.                 if (!$deps) {
  417.                     $depchecker = &$this->getDependency2Object($this->config, $this->getOptions(),
  418.                         $param->getParsedPackage(), PEAR_VALIDATE_DOWNLOADING);
  419.                     if ($param->getType() == 'xmlrpc') {
  420.                         $send = $param->getDownloadURL();
  421.                     } else {
  422.                         $send = $param->getPackageFile();
  423.                     }
  424.                     $installcheck = $depchecker->validatePackage($send, $this, $params);
  425.                     if (PEAR::isError($installcheck)) {
  426.                         if (!isset($this->_options['soft'])) {
  427.                             $this->log(0, $installcheck->getMessage());
  428.                         }
  429.                         $hasfailed = true;
  430.                         $params[$i] = false;
  431.                         $reset = true;
  432.                         $redo = true;
  433.                         $failed = false;
  434.                         PEAR_Downloader_Package::removeDuplicates($params);
  435.                         continue 2;
  436.                     }
  437.                     continue;
  438.                 }
  439.                 if (!$reset && $param->alreadyValidated()) {
  440.                     continue;
  441.                 }
  442.                 if (count($deps)) {
  443.                     $depchecker = &$this->getDependency2Object($this->config, $this->getOptions(),
  444.                         $param->getParsedPackage(), PEAR_VALIDATE_DOWNLOADING);
  445.                     if ($param->getType() == 'xmlrpc') {
  446.                         $send = $param->getDownloadURL();
  447.                     } else {
  448.                         $send = $param->getPackageFile();
  449.                     }
  450.                     $installcheck = $depchecker->validatePackage($send, $this, $params);
  451.                     if (PEAR::isError($installcheck)) {
  452.                         if (!isset($this->_options['soft'])) {
  453.                             $this->log(0, $installcheck->getMessage());
  454.                         }
  455.                         $hasfailed = true;
  456.                         $params[$i] = false;
  457.                         $reset = true;
  458.                         $redo = true;
  459.                         $failed = false;
  460.                         PEAR_Downloader_Package::removeDuplicates($params);
  461.                         continue 2;
  462.                     }
  463.                     $failed = false;
  464.                     if (isset($deps['required'])) {
  465.                         foreach ($deps['required'] as $type => $dep) {
  466.                             // note: Dependency2 will never return a PEAR_Error if ignore-errors
  467.                             // is specified, so soft is needed to turn off logging
  468.                             if (!isset($dep[0])) {
  469.                                 if (PEAR::isError($e = $depchecker->{"validate{$type}Dependency"}($dep,
  470.                                       true, $params))) {
  471.                                     $failed = true;
  472.                                     if (!isset($this->_options['soft'])) {
  473.                                         $this->log(0, $e->getMessage());
  474.                                     }
  475.                                 } elseif (is_array($e) && !$param->alreadyValidated()) {
  476.                                     if (!isset($this->_options['soft'])) {
  477.                                         $this->log(0, $e[0]);
  478.                                     }
  479.                                 }
  480.                             } else {
  481.                                 foreach ($dep as $d) {
  482.                                     if (PEAR::isError($e =
  483.                                           $depchecker->{"validate{$type}Dependency"}($d,
  484.                                           true, $params))) {
  485.                                         $failed = true;
  486.                                         if (!isset($this->_options['soft'])) {
  487.                                             $this->log(0, $e->getMessage());
  488.                                         }
  489.                                     } elseif (is_array($e) && !$param->alreadyValidated()) {
  490.                                         if (!isset($this->_options['soft'])) {
  491.                                             $this->log(0, $e[0]);
  492.                                         }
  493.                                     }
  494.                                 }
  495.                             }
  496.                         }
  497.                         if (isset($deps['optional'])) {
  498.                             foreach ($deps['optional'] as $type => $dep) {
  499.                                 if (!isset($dep[0])) {
  500.                                     if (PEAR::isError($e =
  501.                                           $depchecker->{"validate{$type}Dependency"}($dep,
  502.                                           false, $params))) {
  503.                                         $failed = true;
  504.                                         if (!isset($this->_options['soft'])) {
  505.                                             $this->log(0, $e->getMessage());
  506.                                         }
  507.                                     } elseif (is_array($e) && !$param->alreadyValidated()) {
  508.                                         if (!isset($this->_options['soft'])) {
  509.                                             $this->log(0, $e[0]);
  510.                                         }
  511.                                     }
  512.                                 } else {
  513.                                     foreach ($dep as $d) {
  514.                                         if (PEAR::isError($e =
  515.                                               $depchecker->{"validate{$type}Dependency"}($d,
  516.                                               false, $params))) {
  517.                                             $failed = true;
  518.                                             if (!isset($this->_options['soft'])) {
  519.                                                 $this->log(0, $e->getMessage());
  520.                                             }
  521.                                         } elseif (is_array($e) && !$param->alreadyValidated()) {
  522.                                             if (!isset($this->_options['soft'])) {
  523.                                                 $this->log(0, $e[0]);
  524.                                             }
  525.                                         }
  526.                                     }
  527.                                 }
  528.                             }
  529.                         }
  530.                         $groupname = $param->getGroup();
  531.                         if (isset($deps['group']) && $groupname) {
  532.                             if (!isset($deps['group'][0])) {
  533.                                 $deps['group'] = array($deps['group']);
  534.                             }
  535.                             $found = false;
  536.                             foreach ($deps['group'] as $group) {
  537.                                 if ($group['attribs']['name'] == $groupname) {
  538.                                     $found = true;
  539.                                     break;
  540.                                 }
  541.                             }
  542.                             if ($found) {
  543.                                 unset($group['attribs']);
  544.                                 foreach ($group as $type => $dep) {
  545.                                     if (!isset($dep[0])) {
  546.                                         if (PEAR::isError($e =
  547.                                               $depchecker->{"validate{$type}Dependency"}($dep,
  548.                                               false, $params))) {
  549.                                             $failed = true;
  550.                                             if (!isset($this->_options['soft'])) {
  551.                                                 $this->log(0, $e->getMessage());
  552.                                             }
  553.                                         } elseif (is_array($e) && !$param->alreadyValidated()) {
  554.                                             if (!isset($this->_options['soft'])) {
  555.                                                 $this->log(0, $e[0]);
  556.                                             }
  557.                                         }
  558.                                     } else {
  559.                                         foreach ($dep as $d) {
  560.                                             if (PEAR::isError($e =
  561.                                                   $depchecker->{"validate{$type}Dependency"}($d,
  562.                                                   false, $params))) {
  563.                                                 $failed = true;
  564.                                                 if (!isset($this->_options['soft'])) {
  565.                                                     $this->log(0, $e->getMessage());
  566.                                                 }
  567.                                             } elseif (is_array($e) && !$param->alreadyValidated()) {
  568.                                                 if (!isset($this->_options['soft'])) {
  569.                                                     $this->log(0, $e[0]);
  570.                                                 }
  571.                                             }
  572.                                         }
  573.                                     }
  574.                                 }
  575.                             }
  576.                         }
  577.                     } else {
  578.                         foreach ($deps as $dep) {
  579.                             if (PEAR::isError($e = $depchecker->validateDependency1($dep, $params))) {
  580.                                 $failed = true;
  581.                                 if (!isset($this->_options['soft'])) {
  582.                                     $this->log(0, $e->getMessage());
  583.                                 }
  584.                             } elseif (is_array($e) && !$param->alreadyValidated()) {
  585.                                 if (!isset($this->_options['soft'])) {
  586.                                     $this->log(0, $e[0]);
  587.                                 }
  588.                             }
  589.                         }
  590.                     }
  591.                     $params[$i]->setValidated();
  592.                 }
  593.                 if ($failed) {
  594.                     $hasfailed = true;
  595.                     $params[$i] = false;
  596.                     $reset = true;
  597.                     $redo = true;
  598.                     $failed = false;
  599.                     PEAR_Downloader_Package::removeDuplicates($params);
  600.                     continue 2;
  601.                 }
  602.             }
  603.         }
  604.         PEAR::staticPopErrorHandling();
  605.         if ($hasfailed && (isset($this->_options['ignore-errors']) ||
  606.               isset($this->_options['nodeps']))) {
  607.             // this is probably not needed, but just in case
  608.             if (!isset($this->_options['soft'])) {
  609.                 $this->log(0, 'WARNING: dependencies failed');
  610.             }
  611.         }
  612.     }
  613.  
  614.     /**
  615.      * Retrieve the directory that downloads will happen in
  616.      * @access private
  617.      * @return string
  618.      */
  619.     function getDownloadDir()
  620.     {
  621.         if (isset($this->_downloadDir)) {
  622.             return $this->_downloadDir;
  623.         }
  624.         $downloaddir = $this->config->get('download_dir');
  625.         if (empty($downloaddir)) {
  626.             if (!class_exists('System')) {
  627.                 require_once 'System.php';
  628.             }
  629.             if (PEAR::isError($downloaddir = System::mktemp('-d'))) {
  630.                 return $downloaddir;
  631.             }
  632.             $this->log(3, '+ tmp dir created at ' . $downloaddir);
  633.         }
  634.         return $this->_downloadDir = $downloaddir;
  635.     }
  636.  
  637.     function setDownloadDir($dir)
  638.     {
  639.         $this->_downloadDir = $dir;
  640.     }
  641.  
  642.     // }}}
  643.     // {{{ configSet()
  644.     function configSet($key, $value, $layer = 'user', $channel = false)
  645.     {
  646.         $this->config->set($key, $value, $layer, $channel);
  647.         $this->_preferredState = $this->config->get('preferred_state', null, $channel);
  648.         if (!$this->_preferredState) {
  649.             // don't inadvertantly use a non-set preferred_state
  650.             $this->_preferredState = null;
  651.         }
  652.     }
  653.  
  654.     // }}}
  655.     // {{{ setOptions()
  656.     function setOptions($options)
  657.     {
  658.         $this->_options = $options;
  659.     }
  660.  
  661.     // }}}
  662.     // {{{ setOptions()
  663.     function getOptions()
  664.     {
  665.         return $this->_options;
  666.     }
  667.  
  668.     // }}}
  669.  
  670.     /**
  671.      * For simpler unit-testing
  672.      * @param PEAR_Config
  673.      * @param int
  674.      * @param string
  675.      */
  676.     function &getPackagefileObject(&$c, $d, $t = false)
  677.     {
  678.         if (!class_exists('PEAR_PackageFile')) {
  679.             require_once 'PEAR/PackageFile.php';
  680.         }
  681.         $a = &new PEAR_PackageFile($c, $d, $t);
  682.         return $a;
  683.     }
  684.  
  685.     // {{{ _getPackageDownloadUrl()
  686.  
  687.     /**
  688.      * @param array output of {@link parsePackageName()}
  689.      * @access private
  690.      */
  691.     function _getPackageDownloadUrl($parr)
  692.     {
  693.         $curchannel = $this->config->get('default_channel');
  694.         $this->configSet('default_channel', $parr['channel']);
  695.         // getDownloadURL returns an array.  On error, it only contains information
  696.         // on the latest release as array(version, info).  On success it contains
  697.         // array(version, info, download url string)
  698.         $state = isset($parr['state']) ? $parr['state'] : $this->config->get('preferred_state');
  699.         if (!$this->_registry->channelExists($parr['channel'])) {
  700.             do {
  701.                 if ($this->config->get('auto_discover')) {
  702.                     if ($this->discover($parr['channel'])) {
  703.                         break;
  704.                     }
  705.                 }
  706.                 return PEAR::raiseError('Unknown remote channel: ' . $remotechannel);
  707.             } while (false);
  708.         }
  709.         $chan = &$this->_registry->getChannel($parr['channel']);
  710.         $version = $this->_registry->packageInfo($parr['package'], 'version',
  711.             $parr['channel']);
  712.         if ($chan->supportsREST($this->config->get('preferred_mirror')) &&
  713.               $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))) {
  714.             $rest = &$this->config->getREST('1.0', $this->_options);
  715.             if (!isset($parr['version']) && !isset($parr['state']) && $version
  716.                   && !isset($this->_options['downloadonly'])) {
  717.                 $url = $rest->getDownloadURL($base, $parr, $state, $version);
  718.             } else {
  719.                 $url = $rest->getDownloadURL($base, $parr, $state, false);
  720.             }
  721.             if (PEAR::isError($url)) {
  722.                 return $url;
  723.             }
  724.             if ($parr['channel'] != $curchannel) {
  725.                 $this->configSet('default_channel', $curchannel);
  726.             }
  727.             if (!is_array($url)) {
  728.                 return $url;
  729.             }
  730.             $url['raw'] = false; // no checking is necessary for REST
  731.             if (!is_array($url['info'])) {
  732.                 return PEAR::raiseError('Invalid remote dependencies retrieved from REST - ' .
  733.                     'this should never happen');
  734.             }
  735.             if (isset($url['info']['required']) || $url['compatible']) {
  736.                 require_once 'PEAR/PackageFile/v2.php';
  737.                 $pf = new PEAR_PackageFile_v2;
  738.                 $pf->setRawChannel($parr['channel']);
  739.                 if ($url['compatible']) {
  740.                     $pf->setRawCompatible($url['compatible']);
  741.                 }
  742.             } else {
  743.                 require_once 'PEAR/PackageFile/v1.php';
  744.                 $pf = new PEAR_PackageFile_v1;
  745.             }
  746.             $pf->setRawPackage($url['package']);
  747.             $pf->setDeps($url['info']);
  748.             $pf->setRawState($url['stability']);
  749.             $url['info'] = &$pf;
  750.             if (!extension_loaded("zlib") || isset($this->_options['nocompress'])) {
  751.                 $ext = '.tar';
  752.             } else {
  753.                 $ext = '.tgz';
  754.             }
  755.             if (is_array($url)) {
  756.                 if (isset($url['url'])) {
  757.                     $url['url'] .= $ext;
  758.                 }
  759.             }
  760.             return $url;
  761.         } elseif ($chan->supports('xmlrpc', 'package.getDownloadURL', false, '1.1')) {
  762.             // don't install with the old version information unless we're doing a plain
  763.             // vanilla simple installation.  If the user says to install a particular
  764.             // version or state, ignore the current installed version
  765.             if (!isset($parr['version']) && !isset($parr['state']) && $version
  766.                   && !isset($this->_options['downloadonly'])) {
  767.                 $url = $this->_remote->call('package.getDownloadURL', $parr, $state, $version);
  768.             } else {
  769.                 $url = $this->_remote->call('package.getDownloadURL', $parr, $state);
  770.             }
  771.         } else {
  772.             $url = $this->_remote->call('package.getDownloadURL', $parr, $state);
  773.         }
  774.         if (PEAR::isError($url)) {
  775.             return $url;
  776.         }
  777.         if ($parr['channel'] != $curchannel) {
  778.             $this->configSet('default_channel', $curchannel);
  779.         }
  780.         if (isset($url['__PEAR_ERROR_CLASS__'])) {
  781.             return PEAR::raiseError($url['message']);
  782.         }
  783.         if (!is_array($url)) {
  784.             return $url;
  785.         }
  786.         $url['raw'] = $url['info'];
  787.         if (isset($this->_options['downloadonly'])) {
  788.             $pkg = &$this->getPackagefileObject($this->config, $this->debug);
  789.         } else {
  790.             $pkg = &$this->getPackagefileObject($this->config, $this->debug,
  791.                 $this->getDownloadDir());
  792.         }
  793.         PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
  794.         $pinfo = &$pkg->fromXmlString($url['info'], PEAR_VALIDATE_DOWNLOADING, 'remote');
  795.         PEAR::staticPopErrorHandling();
  796.         if (PEAR::isError($pinfo)) {
  797.             if (!isset($this->_options['soft'])) {
  798.                 $this->log(0, $pinfo->getMessage());
  799.             }
  800.             return PEAR::raiseError('Remote package.xml is not valid - this should never happen');
  801.         }
  802.         $url['info'] = &$pinfo;
  803.         if (!extension_loaded("zlib") || isset($this->_options['nocompress'])) {
  804.             $ext = '.tar';
  805.         } else {
  806.             $ext = '.tgz';
  807.         }
  808.         if (is_array($url)) {
  809.             if (isset($url['url'])) {
  810.                 $url['url'] .= $ext;
  811.             }
  812.         }
  813.         return $url;
  814.     }
  815.     // }}}
  816.     // {{{ getDepPackageDownloadUrl()
  817.  
  818.     /**
  819.      * @param array dependency array
  820.      * @access private
  821.      */
  822.     function _getDepPackageDownloadUrl($dep, $parr)
  823.     {
  824.         $xsdversion = isset($dep['rel']) ? '1.0' : '2.0';
  825.         $curchannel = $this->config->get('default_channel');
  826.         if (isset($dep['channel'])) {
  827.             $remotechannel = $dep['channel'];
  828.         } else {
  829.             $remotechannel = 'pear.php.net';
  830.         }
  831.         if (!$this->_registry->channelExists($remotechannel)) {
  832.             do {
  833.                 if ($this->config->get('auto_discover')) {
  834.                     if ($this->discover($remotechannel)) {
  835.                         break;
  836.                     }
  837.                 }
  838.                 return PEAR::raiseError('Unknown remote channel: ' . $remotechannel);
  839.             } while (false);
  840.         }
  841.         $this->configSet('default_channel', $remotechannel);
  842.         $state = isset($parr['state']) ? $parr['state'] : $this->config->get('preferred_state');
  843.         if (isset($parr['state']) && isset($parr['version'])) {
  844.             unset($parr['state']);
  845.         }
  846.         $chan = &$this->_registry->getChannel($remotechannel);
  847.         $version = $this->_registry->packageInfo($dep['name'], 'version',
  848.             $remotechannel);
  849.         if ($chan->supportsREST($this->config->get('preferred_mirror')) &&
  850.               $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))) {
  851.             $rest = &$this->config->getREST('1.0', $this->_options);
  852.             $url = $rest->getDepDownloadURL($base, $xsdversion, $dep, $parr,
  853.                     $state, $version);
  854.             if (PEAR::isError($url)) {
  855.                 return $url;
  856.             }
  857.             if ($parr['channel'] != $curchannel) {
  858.                 $this->configSet('default_channel', $curchannel);
  859.             }
  860.             if (!is_array($url)) {
  861.                 return $url;
  862.             }
  863.             $url['raw'] = false; // no checking is necessary for REST
  864.             if (!is_array($url['info'])) {
  865.                 return PEAR::raiseError('Invalid remote dependencies retrieved from REST - ' .
  866.                     'this should never happen');
  867.             }
  868.             if (isset($url['info']['required'])) {
  869.                 if (!class_exists('PEAR_PackageFile_v2')) {
  870.                     require_once 'PEAR/PackageFile/v2.php';
  871.                 }
  872.                 $pf = new PEAR_PackageFile_v2;
  873.                 $pf->setRawChannel($remotechannel);
  874.             } else {
  875.                 if (!class_exists('PEAR_PackageFile_v1')) {
  876.                     require_once 'PEAR/PackageFile/v1.php';
  877.                 }
  878.                 $pf = new PEAR_PackageFile_v1;
  879.             }
  880.             $pf->setRawPackage($url['package']);
  881.             $pf->setDeps($url['info']);
  882.             $pf->setRawState($url['stability']);
  883.             $url['info'] = &$pf;
  884.             if (!extension_loaded("zlib") || isset($this->_options['nocompress'])) {
  885.                 $ext = '.tar';
  886.             } else {
  887.                 $ext = '.tgz';
  888.             }
  889.             if (is_array($url)) {
  890.                 if (isset($url['url'])) {
  891.                     $url['url'] .= $ext;
  892.                 }
  893.             }
  894.             return $url;
  895.         } elseif ($chan->supports('xmlrpc', 'package.getDepDownloadURL', false, '1.1')) {
  896.             if ($version) {
  897.                 $url = $this->_remote->call('package.getDepDownloadURL', $xsdversion, $dep, $parr,
  898.                     $state, $version);
  899.             } else {
  900.                 $url = $this->_remote->call('package.getDepDownloadURL', $xsdversion, $dep, $parr,
  901.                     $state);
  902.             }
  903.         } else {
  904.             $url = $this->_remote->call('package.getDepDownloadURL', $xsdversion, $dep, $parr, $state);
  905.         }
  906.         if ($parr['channel'] != $curchannel) {
  907.             $this->configSet('default_channel', $curchannel);
  908.         }
  909.         if (!is_array($url)) {
  910.             return $url;
  911.         }
  912.         if (isset($url['__PEAR_ERROR_CLASS__'])) {
  913.             return PEAR::raiseError($url['message']);
  914.         }
  915.         $url['raw'] = $url['info'];
  916.         $pkg = &$this->getPackagefileObject($this->config, $this->debug);
  917.         PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
  918.         $pinfo = &$pkg->fromXmlString($url['info'], PEAR_VALIDATE_DOWNLOADING, 'remote');
  919.         PEAR::staticPopErrorHandling();
  920.         if (PEAR::isError($pinfo)) {
  921.             if (!isset($this->_options['soft'])) {
  922.                 $this->log(0, $pinfo->getMessage());
  923.             }
  924.             return PEAR::raiseError('Remote package.xml is not valid - this should never happen');
  925.         }
  926.         $url['info'] = &$pinfo;
  927.         if (is_array($url)) {
  928.             if (!extension_loaded("zlib") || isset($this->_options['nocompress'])) {
  929.                 $ext = '.tar';
  930.             } else {
  931.                 $ext = '.tgz';
  932.             }
  933.             if (isset($url['url'])) {
  934.                 $url['url'] .= $ext;
  935.             }
  936.         }
  937.         return $url;
  938.     }
  939.     // }}}
  940.     // {{{ getPackageDownloadUrl()
  941.  
  942.     /**
  943.      * @deprecated in favor of _getPackageDownloadUrl
  944.      */
  945.     function getPackageDownloadUrl($package, $version = null, $channel = false)
  946.     {
  947.         if ($version) {
  948.             $package .= "-$version";
  949.         }
  950.         if ($this === null || $this->_registry === null) {
  951.             $package = "http://pear.php.net/get/$package";
  952.         } else {
  953.             $chan = $this->_registry->getChannel($channel);
  954.             $package = "http://" . $chan->getServer() . "/get/$package";
  955.         }
  956.         if (!extension_loaded("zlib")) {
  957.             $package .= '?uncompress=yes';
  958.         }
  959.         return $package;
  960.     }
  961.  
  962.     // }}}
  963.     // {{{ getDownloadedPackages()
  964.  
  965.     /**
  966.      * Retrieve a list of downloaded packages after a call to {@link download()}.
  967.      *
  968.      * Also resets the list of downloaded packages.
  969.      * @return array
  970.      */
  971.     function getDownloadedPackages()
  972.     {
  973.         $ret = $this->_downloadedPackages;
  974.         $this->_downloadedPackages = array();
  975.         $this->_toDownload = array();
  976.         return $ret;
  977.     }
  978.  
  979.     // }}}
  980.     // {{{ _downloadCallback()
  981.  
  982.     function _downloadCallback($msg, $params = null)
  983.     {
  984.         switch ($msg) {
  985.             case 'saveas':
  986.                 $this->log(1, "downloading $params ...");
  987.                 break;
  988.             case 'done':
  989.                 $this->log(1, '...done: ' . number_format($params, 0, '', ',') . ' bytes');
  990.                 break;
  991.             case 'bytesread':
  992.                 static $bytes;
  993.                 if (empty($bytes)) {
  994.                     $bytes = 0;
  995.                 }
  996.                 if (!($bytes % 10240)) {
  997.                     $this->log(1, '.', false);
  998.                 }
  999.                 $bytes += $params;
  1000.                 break;
  1001.             case 'start':
  1002.                 $this->log(1, "Starting to download {$params[0]} (".number_format($params[1], 0, '', ',')." bytes)");
  1003.                 break;
  1004.         }
  1005.         if (method_exists($this->ui, '_downloadCallback'))
  1006.             $this->ui->_downloadCallback($msg, $params);
  1007.     }
  1008.  
  1009.     // }}}
  1010.     // {{{ _prependPath($path, $prepend)
  1011.  
  1012.     function _prependPath($path, $prepend)
  1013.     {
  1014.         if (strlen($prepend) > 0) {
  1015.             if (OS_WINDOWS && preg_match('/^[a-z]:/i', $path)) {
  1016.                 if (preg_match('/^[a-z]:/i', $prepend)) {
  1017.                     $prepend = substr($prepend, 2);
  1018.                 } elseif ($prepend{0} != '\\') {
  1019.                     $prepend = "\\$prepend";
  1020.                 }
  1021.                 $path = substr($path, 0, 2) . $prepend . substr($path, 2);
  1022.             } else {
  1023.                 $path = $prepend . $path;
  1024.             }
  1025.         }
  1026.         return $path;
  1027.     }
  1028.     // }}}
  1029.     // {{{ pushError($errmsg, $code)
  1030.  
  1031.     /**
  1032.      * @param string
  1033.      * @param integer
  1034.      */
  1035.     function pushError($errmsg, $code = -1)
  1036.     {
  1037.         array_push($this->_errorStack, array($errmsg, $code));
  1038.     }
  1039.  
  1040.     // }}}
  1041.     // {{{ getErrorMsgs()
  1042.  
  1043.     function getErrorMsgs()
  1044.     {
  1045.         $msgs = array();
  1046.         $errs = $this->_errorStack;
  1047.         foreach ($errs as $err) {
  1048.             $msgs[] = $err[0];
  1049.         }
  1050.         $this->_errorStack = array();
  1051.         return $msgs;
  1052.     }
  1053.  
  1054.     // }}}
  1055.  
  1056.     /**
  1057.      * for BC
  1058.      */
  1059.     function sortPkgDeps(&$packages, $uninstall = false)
  1060.     {
  1061.         $uninstall ? 
  1062.             $this->sortPackagesForUninstall($packages) :
  1063.             $this->sortPackagesForInstall($packages);
  1064.     }
  1065.  
  1066.     function _getDepTreeDP($package, $packages, &$deps, &$checked)
  1067.     {
  1068.         $pf = $package->getPackageFile();
  1069.         $checked[strtolower($package->getChannel())][strtolower($package->getPackage())]
  1070.             = true;
  1071.         $pdeps = $pf->getDeps(true);
  1072.         if (!$pdeps) {
  1073.             return;
  1074.         }
  1075.         if ($pf->getPackagexmlVersion() == '1.0') {
  1076.             foreach ($pdeps as $dep) {
  1077.                 if ($dep['type'] != 'pkg') {
  1078.                     continue;
  1079.                 }
  1080.                 $deps['pear.php.net'][strtolower($dep['name'])] = true;
  1081.                 foreach ($packages as $p) {
  1082.                     $dep['channel'] = 'pear.php.net';
  1083.                     $dep['package'] = $dep['name'];
  1084.                     if ($p->isEqual($dep)) {
  1085.                         if (!isset($checked[strtolower($p->getChannel())]
  1086.                               [strtolower($p->getPackage())])) {
  1087.                             // add the dependency's dependencies to the tree
  1088.                             $this->_getDepTreeDP($p, $packages, $deps, $checked);
  1089.                         }
  1090.                     }
  1091.                 }
  1092.             }
  1093.         } else {
  1094.             $tdeps = array();
  1095.             if (isset($pdeps['required']['package'])) {
  1096.                 $t = $pdeps['required']['package'];
  1097.                 if (!isset($t[0])) {
  1098.                     $t = array($t);
  1099.                 }
  1100.                 $tdeps = array_merge($tdeps, $t);
  1101.             }
  1102.             if (isset($pdeps['required']['subpackage'])) {
  1103.                 $t = $pdeps['required']['subpackage'];
  1104.                 if (!isset($t[0])) {
  1105.                     $t = array($t);
  1106.                 }
  1107.                 $tdeps = array_merge($tdeps, $t);
  1108.             }
  1109.             if (isset($pdeps['optional']['package'])) {
  1110.                 $t = $pdeps['optional']['package'];
  1111.                 if (!isset($t[0])) {
  1112.                     $t = array($t);
  1113.                 }
  1114.                 $tdeps = array_merge($tdeps, $t);
  1115.             }
  1116.             if (isset($pdeps['optional']['subpackage'])) {
  1117.                 $t = $pdeps['optional']['subpackage'];
  1118.                 if (!isset($t[0])) {
  1119.                     $t = array($t);
  1120.                 }
  1121.                 $tdeps = array_merge($tdeps, $t);
  1122.             }
  1123.             if (isset($pdeps['group'])) {
  1124.                 if (!isset($pdeps['group'][0])) {
  1125.                     $pdeps['group'] = array($pdeps['group']);
  1126.                 }
  1127.                 foreach ($pdeps['group'] as $group) {
  1128.                     if (isset($group['package'])) {
  1129.                         $t = $group['package'];
  1130.                         if (!isset($t[0])) {
  1131.                             $t = array($t);
  1132.                         }
  1133.                         $tdeps = array_merge($tdeps, $t);
  1134.                     }
  1135.                     if (isset($group['subpackage'])) {
  1136.                         $t = $group['subpackage'];
  1137.                         if (!isset($t[0])) {
  1138.                             $t = array($t);
  1139.                         }
  1140.                         $tdeps = array_merge($tdeps, $t);
  1141.                     }
  1142.                 }
  1143.             }
  1144.             foreach ($tdeps as $dep) {
  1145.                 if (!isset($dep['channel'])) {
  1146.                     $depchannel = '__uri';
  1147.                 } else {
  1148.                     $depchannel = $dep['channel'];
  1149.                 }
  1150.                 $deps[$depchannel][strtolower($dep['name'])] = true;
  1151.                 foreach ($packages as $p) {
  1152.                     $dep['channel'] = $depchannel;
  1153.                     $dep['package'] = $dep['name'];
  1154.                     if ($p->isEqual($dep)) {
  1155.                         if (!isset($checked[strtolower($p->getChannel())]
  1156.                               [strtolower($p->getPackage())])) {
  1157.                             // add the dependency's dependencies to the tree
  1158.                             $this->_getDepTreeDP($p, $packages, $deps, $checked);
  1159.                         }
  1160.                     }
  1161.                 }
  1162.             }
  1163.         }
  1164.     }
  1165.  
  1166.     /**
  1167.      * Sort a list of arrays of array(downloaded packagefilename) by dependency.
  1168.      *
  1169.      * It also removes duplicate dependencies
  1170.      * @param array an array of downloaded PEAR_Downloader_Packages
  1171.      * @return array array of array(packagefilename, package.xml contents)
  1172.      */
  1173.     function sortPackagesForInstall(&$packages)
  1174.     {
  1175.         foreach ($packages as $i => $package) {
  1176.             $checked = $deps = array();
  1177.             $this->_getDepTreeDP($packages[$i], $packages, $deps, $checked);
  1178.             $this->_depTree[$package->getChannel()][$package->getPackage()] = $deps;
  1179.         }
  1180.         usort($packages, array(&$this, '_sortInstall'));
  1181.     }
  1182.  
  1183.     function _dependsOn($a, $b)
  1184.     {
  1185.         return $this->_checkDepTree(strtolower($a->getChannel()), strtolower($a->getPackage()),
  1186.             $b);
  1187.     }
  1188.  
  1189.     function _checkDepTree($channel, $package, $b, $checked = array())
  1190.     {
  1191.         $checked[$channel][$package] = true;
  1192.         if (!isset($this->_depTree[$channel][$package])) {
  1193.             return false;
  1194.         }
  1195.         if (isset($this->_depTree[$channel][$package][strtolower($b->getChannel())]
  1196.               [strtolower($b->getPackage())])) {
  1197.             return true;
  1198.         }
  1199.         foreach ($this->_depTree[$channel][$package] as $ch => $packages) {
  1200.             foreach ($packages as $pa => $true) {
  1201.                 if ($this->_checkDepTree($ch, $pa, $b, $checked)) {
  1202.                     return true;
  1203.                 }
  1204.             }
  1205.         }
  1206.         return false;
  1207.     }
  1208.  
  1209.     function _sortInstall($a, $b)
  1210.     {
  1211.         if (!$a->getDeps() && !$b->getDeps()) {
  1212.             return 0; // neither package has dependencies, order is insignificant
  1213.         }
  1214.         if ($a->getDeps() && !$b->getDeps()) {
  1215.             return 1; // $a must be installed after $b because $a has dependencies
  1216.         }
  1217.         if (!$a->getDeps() && $b->getDeps()) {
  1218.             return -1; // $b must be installed after $a because $b has dependencies
  1219.         }
  1220.         // both packages have dependencies
  1221.         if ($this->_dependsOn($a, $b)) {
  1222.             return 1;
  1223.         }
  1224.         if ($this->_dependsOn($b, $a)) {
  1225.             return -1;
  1226.         }
  1227.         return 0;
  1228.     }
  1229.  
  1230.     /**
  1231.      * Download a file through HTTP.  Considers suggested file name in
  1232.      * Content-disposition: header and can run a callback function for
  1233.      * different events.  The callback will be called with two
  1234.      * parameters: the callback type, and parameters.  The implemented
  1235.      * callback types are:
  1236.      *
  1237.      *  'setup'       called at the very beginning, parameter is a UI object
  1238.      *                that should be used for all output
  1239.      *  'message'     the parameter is a string with an informational message
  1240.      *  'saveas'      may be used to save with a different file name, the
  1241.      *                parameter is the filename that is about to be used.
  1242.      *                If a 'saveas' callback returns a non-empty string,
  1243.      *                that file name will be used as the filename instead.
  1244.      *                Note that $save_dir will not be affected by this, only
  1245.      *                the basename of the file.
  1246.      *  'start'       download is starting, parameter is number of bytes
  1247.      *                that are expected, or -1 if unknown
  1248.      *  'bytesread'   parameter is the number of bytes read so far
  1249.      *  'done'        download is complete, parameter is the total number
  1250.      *                of bytes read
  1251.      *  'connfailed'  if the TCP/SSL connection fails, this callback is called
  1252.      *                with array(host,port,errno,errmsg)
  1253.      *  'writefailed' if writing to disk fails, this callback is called
  1254.      *                with array(destfile,errmsg)
  1255.      *
  1256.      * If an HTTP proxy has been configured (http_proxy PEAR_Config
  1257.      * setting), the proxy will be used.
  1258.      *
  1259.      * @param string  $url       the URL to download
  1260.      * @param object  $ui        PEAR_Frontend_* instance
  1261.      * @param object  $config    PEAR_Config instance
  1262.      * @param string  $save_dir  directory to save file in
  1263.      * @param mixed   $callback  function/method to call for status
  1264.      *                           updates
  1265.      * @param false|string|array $lastmodified header values to check against for caching
  1266.      *                           use false to return the header values from this download
  1267.      * @param false|array $accept Accept headers to send
  1268.      * @return string|array  Returns the full path of the downloaded file or a PEAR
  1269.      *                       error on failure.  If the error is caused by
  1270.      *                       socket-related errors, the error object will
  1271.      *                       have the fsockopen error code available through
  1272.      *                       getCode().  If caching is requested, then return the header
  1273.      *                       values.
  1274.      *
  1275.      * @access public
  1276.      */
  1277.     function downloadHttp($url, &$ui, $save_dir = '.', $callback = null, $lastmodified = null,
  1278.                           $accept = false)
  1279.     {
  1280.         if ($callback) {
  1281.             call_user_func($callback, 'setup', array(&$ui));
  1282.         }
  1283.         $info = parse_url($url);
  1284.         if (!isset($info['scheme']) || !in_array($info['scheme'], array('http', 'https'))) {
  1285.             return PEAR::raiseError('Cannot download non-http URL "' . $url . '"');
  1286.         }
  1287.         if (!isset($info['host'])) {
  1288.             return PEAR::raiseError('Cannot download from non-URL "' . $url . '"');
  1289.         } else {
  1290.             $host = @$info['host'];
  1291.             $port = @$info['port'];
  1292.             $path = @$info['path'];
  1293.         }
  1294.         if (isset($this)) {
  1295.             $config = &$this->config;
  1296.         } else {
  1297.             $config = &PEAR_Config::singleton();
  1298.         }
  1299.         $proxy_host = $proxy_port = $proxy_user = $proxy_pass = '';
  1300.         if ($config->get('http_proxy')&& 
  1301.               $proxy = parse_url($config->get('http_proxy'))) {
  1302.             $proxy_host = @$proxy['host'];
  1303.             if (isset($proxy['scheme']) && $proxy['scheme'] == 'https') {
  1304.                 $proxy_host = 'ssl://' . $proxy_host;
  1305.             }
  1306.             $proxy_port = @$proxy['port'];
  1307.             $proxy_user = @$proxy['user'];
  1308.             $proxy_pass = @$proxy['pass'];
  1309.  
  1310.             if ($proxy_port == '') {
  1311.                 $proxy_port = 8080;
  1312.             }
  1313.             if ($callback) {
  1314.                 call_user_func($callback, 'message', "Using HTTP proxy $host:$port");
  1315.             }
  1316.         }
  1317.         if (empty($port)) {
  1318.             if (isset($info['scheme']) && $info['scheme'] == 'https') {
  1319.                 $port = 443;
  1320.             } else {
  1321.                 $port = 80;
  1322.             }
  1323.         }
  1324.         if ($proxy_host != '') {
  1325.             $fp = @fsockopen($proxy_host, $proxy_port, $errno, $errstr);
  1326.             if (!$fp) {
  1327.                 if ($callback) {
  1328.                     call_user_func($callback, 'connfailed', array($proxy_host, $proxy_port,
  1329.                                                                   $errno, $errstr));
  1330.                 }
  1331.                 return PEAR::raiseError("Connection to `$proxy_host:$proxy_port' failed: $errstr", $errno);
  1332.             }
  1333.             if ($lastmodified === false || $lastmodified) {
  1334.                 $request = "GET $url HTTP/1.1\r\n";
  1335.             } else {
  1336.                 $request = "GET $url HTTP/1.0\r\n";
  1337.             }
  1338.         } else {
  1339.             if (isset($info['scheme']) && $info['scheme'] == 'https') {
  1340.                 $host = 'ssl://' . $host;
  1341.             }
  1342.             $fp = @fsockopen($host, $port, $errno, $errstr);
  1343.             if (!$fp) {
  1344.                 if ($callback) {
  1345.                     call_user_func($callback, 'connfailed', array($host, $port,
  1346.                                                                   $errno, $errstr));
  1347.                 }
  1348.                 return PEAR::raiseError("Connection to `$host:$port' failed: $errstr", $errno);
  1349.             }
  1350.             if ($lastmodified === false || $lastmodified) {
  1351.                 $request = "GET $path HTTP/1.1\r\n";
  1352.             } else {
  1353.                 $request = "GET $path HTTP/1.0\r\n";
  1354.             }
  1355.         }
  1356.         $ifmodifiedsince = '';
  1357.         if (is_array($lastmodified)) {
  1358.             if (isset($lastmodified['Last-Modified'])) {
  1359.                 $ifmodifiedsince = 'If-Modified-Since: ' . $lastmodified['Last-Modified'] . "\r\n";
  1360.             }
  1361.             if (isset($lastmodified['ETag'])) {
  1362.                 $ifmodifiedsince .= "If-None-Match: $lastmodified[ETag]\r\n";
  1363.             }
  1364.         } else {
  1365.             $ifmodifiedsince = ($lastmodified ? "If-Modified-Since: $lastmodified\r\n" : '');
  1366.         }
  1367.         $request .= "Host: $host:$port\r\n" . $ifmodifiedsince .
  1368.             "User-Agent: PEAR/1.4.5/PHP/" . PHP_VERSION . "\r\n";
  1369.         if (isset($this)) { // only pass in authentication for non-static calls
  1370.             $username = $config->get('username');
  1371.             $password = $config->get('password');
  1372.             if ($username && $password) {
  1373.                 $tmp = base64_encode("$username:$password");
  1374.                 $request .= "Authorization: Basic $tmp\r\n";
  1375.             }
  1376.         }
  1377.         if ($proxy_host != '' && $proxy_user != '') {
  1378.             $request .= 'Proxy-Authorization: Basic ' .
  1379.                 base64_encode($proxy_user . ':' . $proxy_pass) . "\r\n";
  1380.         }
  1381.         if ($accept) {
  1382.             $request .= 'Accept: ' . implode(', ', $accept) . "\r\n";
  1383.         }
  1384.         $request .= "Connection: close\r\n";
  1385.         $request .= "\r\n";
  1386.         fwrite($fp, $request);
  1387.         $headers = array();
  1388.         while (trim($line = fgets($fp, 1024))) {
  1389.             if (preg_match('/^([^:]+):\s+(.*)\s*$/', $line, $matches)) {
  1390.                 $headers[strtolower($matches[1])] = trim($matches[2]);
  1391.             } elseif (preg_match('|^HTTP/1.[01] ([0-9]{3}) |', $line, $matches)) {
  1392.                 if ($matches[1] == 304 && ($lastmodified || ($lastmodified === false))) {
  1393.                     return false;
  1394.                 }
  1395.                 if ($matches[1] != 200) {
  1396.                     return PEAR::raiseError("File http://$host:$port$path not valid (received: $line)");
  1397.                 }
  1398.             }
  1399.         }
  1400.         if (isset($headers['content-disposition']) &&
  1401.             preg_match('/\sfilename=\"([^;]*\S)\"\s*(;|$)/', $headers['content-disposition'], $matches)) {
  1402.             $save_as = basename($matches[1]);
  1403.         } else {
  1404.             $save_as = basename($url);
  1405.         }
  1406.         if ($callback) {
  1407.             $tmp = call_user_func($callback, 'saveas', $save_as);
  1408.             if ($tmp) {
  1409.                 $save_as = $tmp;
  1410.             }
  1411.         }
  1412.         $dest_file = $save_dir . DIRECTORY_SEPARATOR . $save_as;
  1413.         if (!$wp = @fopen($dest_file, 'wb')) {
  1414.             fclose($fp);
  1415.             if ($callback) {
  1416.                 call_user_func($callback, 'writefailed', array($dest_file, $php_errormsg));
  1417.             }
  1418.             return PEAR::raiseError("could not open $dest_file for writing");
  1419.         }
  1420.         if (isset($headers['content-length'])) {
  1421.             $length = $headers['content-length'];
  1422.         } else {
  1423.             $length = -1;
  1424.         }
  1425.         $bytes = 0;
  1426.         if ($callback) {
  1427.             call_user_func($callback, 'start', array(basename($dest_file), $length));
  1428.         }
  1429.         while ($data = @fread($fp, 1024)) {
  1430.             $bytes += strlen($data);
  1431.             if ($callback) {
  1432.                 call_user_func($callback, 'bytesread', $bytes);
  1433.             }
  1434.             if (!@fwrite($wp, $data)) {
  1435.                 fclose($fp);
  1436.                 if ($callback) {
  1437.                     call_user_func($callback, 'writefailed', array($dest_file, $php_errormsg));
  1438.                 }
  1439.                 return PEAR::raiseError("$dest_file: write failed ($php_errormsg)");
  1440.             }
  1441.         }
  1442.         fclose($fp);
  1443.         fclose($wp);
  1444.         if ($callback) {
  1445.             call_user_func($callback, 'done', $bytes);
  1446.         }
  1447.         if ($lastmodified === false || $lastmodified) {
  1448.             if (isset($headers['etag'])) {
  1449.                 $lastmodified = array('ETag' => $headers['etag']);
  1450.             }
  1451.             if (isset($headers['last-modified'])) {
  1452.                 if (is_array($lastmodified)) {
  1453.                     $lastmodified['Last-Modified'] = $headers['last-modified'];
  1454.                 } else {
  1455.                     $lastmodified = $headers['last-modified'];
  1456.                 }
  1457.             }
  1458.             return array($dest_file, $lastmodified, $headers);
  1459.         }
  1460.         return $dest_file;
  1461.     }
  1462. }
  1463. // }}}
  1464.  
  1465. ?>